<?php
namespace Lsf\Plugin;

/**
 * 验证码插件类
 * @author zhuliu@okay.cn
 * $Id: verify_code.php 2019-11-28 17:37:15 zhuliu $
 */

class VerifyCode
{
    const ERR_CODE_ERROR        = -1;               // 验证失败
    const ERR_CODE_TIMEOUT      = -2;               // 验证超时
    const MIN_DIGIT             = 4;                // 最小位数
    const MAX_DIGIT             = 8;                // 最大位数
    const MIN_EXPIRE            = 60;               // 最小超时时间 1m
    const MAX_EXPIRE            = 900;              // 最大超时时间 15m
    const REDIS_SIGN            = 'verify_code';    // redis标识

    private $_config            = [];
    private $_serverName        = '';   // 应用名称
    private $_redisKeyPrefix    = '';   // redis前缀

    /**
     * 构造方法
     * @param  void
     * @return void
     * @throws \Exception
     */
    public function __construct(){
        // 服务名称
        $serverConfig = \Lsf\Loader::config('server', TRUE);
        $this->_serverName = $serverConfig['name'];
        // 插件配置
        $lsfConfig = \Lsf\Loader::config('lsf', TRUE);
        if (!isset($lsfConfig['plugin']['verify_code']) || empty($lsfConfig['plugin']['verify_code'])){
            throw new \Exception('lsf config plugin verify_code not define');
        }
        $this->_config = $lsfConfig['plugin']['verify_code'];
        $this->_redisKeyPrefix = $this->_serverName . ':' . self::REDIS_SIGN . ':';
        // 检查插件配置参数
        foreach ($this->_config as $key => $value){
            if (!isset($value['digit']) || empty($value['digit'])){
                throw new \Exception('lsf config plugin verify_code ' . $key . ' miss digit');
            }
            if (!isset($value['expire_time']) || empty($value['expire_time'])){
                throw new \Exception('lsf config plugin verify_code ' . $key . ' miss expire_time');
            }
            if ($value['digit'] < self::MIN_DIGIT || $value['digit'] > self::MAX_DIGIT){
                throw new \Exception('lsf config plugin verify_code digit out of range [' . self::MIN_DIGIT . ',' . self::MAX_DIGIT . ']');
            }
            if ($value['expire_time'] < self::MIN_EXPIRE || $value['expire_time'] > self::MAX_EXPIRE){
                throw new \Exception('lsf config plugin verify_code expire_time out of range [' . self::MIN_EXPIRE . ',' . self::MAX_EXPIRE . '],unit is second');
            }
        }
    }

    /**
     * 生成验证码
     * @param  string  $sceneKey
     * @param  string  $uniqueId
     * @param  string  $externalCode
     * @return string
     * @throws \Exception
     */
    public function generate($sceneKey = '', $uniqueId = '', $externalCode = ''){
        // 场景未配置
        if(!isset($this->_config[$sceneKey])){
            throw new \Exception('lsf config plugin verify_code scene ' . $sceneKey . ' not define');
        }
        // 位数
        $digit = $this->_config[$sceneKey]['digit'];
        // 过期时间
        $expireTime = $this->_config[$sceneKey]['expire_time'];
        // 生成验证码
        $code = '';
        try{
            // 随机数 eg:4位 mt_rand(1000,9999)
            $code = $externalCode == '' ? mt_rand(pow(10, $digit - 1), pow(10, $digit) - 1) : $externalCode;
            $redisKey = $this->_getRedisKey($sceneKey, $uniqueId);
            $result = \Lsf\Loader::plugin('RedisPool')->redis()->setex($redisKey, $expireTime, $code);
            if ($result !== TRUE){
                throw new \Exception('lsf plugin verify_code redis set fail');
            }
        }catch (\Exception $e){
            \Lsf\Loader::plugin('Log')->error(9000019, ['call_function' => 'generate', 'error' => $e]);
            throw new \Exception('lsf plugin verify_code generate fail');
        }
        return $code;
    }

    /**
     * 验证
     * @param  string  $sceneKey
     * @param  string  $uniqueId
     * @param  string  $code
     * @return int                0-正确 -1-验证失败 -2-验证超时
     * @throws \Exception
     */
    public function verify($sceneKey = '', $uniqueId = '', $code = ''){
        $redisKey = $this->_getRedisKey($sceneKey, $uniqueId);
        try{
            $sessionCode = \Lsf\Loader::plugin('RedisPool')->redis()->get($redisKey);
            // key不存在则code失效
            if($sessionCode === FALSE){
                return self::ERR_CODE_TIMEOUT;
            }
            return $sessionCode == $code ? 0 : self::ERR_CODE_ERROR;
        }catch (\Exception $e){
            \Lsf\Loader::plugin('Log')->error(9000019, ['call_function' => 'verify', 'error' => $e]);
            throw new \Exception('lsf plugin verify_code verify fail');
        }
    }

    /**
     * 生成redis的key
     * @param  string  $sceneKey
     * @param  string  $uniqueId
     * @return string
     */
    private function _getRedisKey($sceneKey, $uniqueId){
        $key = $this->_redisKeyPrefix;
        $key .= $sceneKey;
        $key .= ':';
        $key .= $uniqueId;
        return $key;
    }
}